/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package boundless.ejb;
///Coomment
import javax.ejb.Stateless;
import javax.ejb.LocalBean;
import boundless.entities.*;
import boundless.entities.Playposition;
import boundless.scheduler.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.annotation.security.RolesAllowed;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.Singleton;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.*;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.*;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;

/**
 *
 * @author Matt
 */
//@Stateless
@Singleton
@Named
//@LocalBean
public class RequestBean {

    private static final Logger logger = Logger.getLogger(
                "boundless.ejb.RequestBean");
    @Resource
    SessionContext ctx;
    private CriteriaBuilder cb;
    @PersistenceContext
    private EntityManager em;

    
    //Constructor to emulate BookRequestBean
    /*public RequestBean() throws Exception {
        
    }*/
    
    @PostConstruct
    private void init() {
        cb = em.getCriteriaBuilder();
    }

    public void synchronize() {
        em.flush();
    }
    
    public String logIn(Account acc) {
        
        return null;
    }
    
    public String register(Account acc) {
        
        return null;
    }

    /*User Methods*/
    
    public Account getAccountById(int accId) {
        return em.find(Account.class, accId);
    }
    
    public Account getUserByUsername(String username) {
        CriteriaQuery<Account> cq = em.getCriteriaBuilder().createQuery(Account.class);
        Root<Account> acc = cq.from(Account.class);
        cq.select(acc);
        cq.where(
                cb.equal(acc.get("username"), username));
        TypedQuery<Account> q = em.createQuery(cq);
        
        try {
            return q.getSingleResult();
        }
        catch (NoResultException ne) {
            return null;
        }
        
    }
    
    public Account getAccountByEmail(String email) {
        CriteriaQuery<Account> cq = em.getCriteriaBuilder().createQuery(Account.class);
        Root<Account> acc = cq.from(Account.class);
        cq.select(acc);
        cq.where(
                cb.equal(acc.get("email"), email));
        TypedQuery<Account> q = em.createQuery(cq);
        
        try {
            return q.getSingleResult();
        }
        catch (NoResultException ne) {
            return null;
        }
    }
    
    /**
     * Returns the account if it is found, a null otherwise
     * @param username : The username to search for
     * @param password : The corresponding password
     * @return : The account if it exists, null otherwise
     */
    public Account isValidUserPassPair(String username, String password) {
        CriteriaQuery<Account> cq = em.getCriteriaBuilder().createQuery(Account.class);
        Root<Account> acc = cq.from(Account.class);
        cq.select(acc);
        cq.where(
		 cb.equal(acc.get("username"), username),
		 cb.equal(acc.get("password"), password));
        TypedQuery<Account> q = em.createQuery(cq);
        
        try {
            return q.getSingleResult();
        }
        catch (NoResultException ne) {
            return null;
        }
    }
    
    public List<Invitebyid> getInvitationsForUser(Account acc) {
        
        CriteriaQuery cq = em.getCriteriaBuilder().createQuery(Invitebyid.class);
        Root<Invitebyid> invites = cq.from(Invitebyid.class);
        cq.select(invites);
        cq.where(
                cb.equal(
                invites.get("invitebyidPK").get("inviteid"), acc.getId()));
        TypedQuery<Invitebyid> q = em.createQuery(cq);
        
        return q.getResultList();
    }
    
    public Playposition getPosition(int pid) {
        return em.find(Playposition.class, pid);
    }
    
    /*League Methods*/
    public League getLeague(int lid) {
        return em.find(League.class, lid);
    }
    
    public League getLeagueByName(String name) {
        CriteriaQuery<League> cq = em.getCriteriaBuilder().createQuery(League.class);
        Root<League> league = cq.from(League.class);
        cq.select(league);
        cq.where(
                cb.equal(league.get("name"), name));
        TypedQuery<League> q = em.createQuery(cq);
        try {
            return q.getSingleResult();
        }
        catch (NoResultException ne) {
            return null;
        }
    }
    
    public List<League> getAllLeagues() {
        TypedQuery<League> q = em.createNamedQuery("League.findAll", League.class);
        return q.getResultList();
    }
    
    public List<League> getLeaguesByOwner(Account acc) {
        CriteriaQuery<League> cq = em.getCriteriaBuilder().createQuery(League.class);
        Root<League> league = cq.from(League.class);
        cq.select(league);
        cq.where(
                cb.equal(league.get("ownerid"), acc));
        TypedQuery<League> q = em.createQuery(cq);
        return q.getResultList();
    }

    
    public boolean createLeague(Account owner, String name, boolean inviteOnly) {
        Integer maxNum;
        maxNum = (Integer) em.createNativeQuery("SELECT MAX(Id) FROM LEAGUE").getSingleResult();
        int newLeagueId = maxNum + 1;
        
        League l = new League(newLeagueId, name);
        l.setOwnerid(owner);
        if(inviteOnly) {
            l.setInviteonly((short) 1);
        }
        else {
            l.setInviteonly((short) 0);
        }
        
        try {
         em.persist(l);
        }
        catch(EntityExistsException eee) {
            System.out.println("This league already exists");
            return false;
        }
        return true;
        
    }
    
    /*public boolean addUserToLeague(Account user, League l) {
        Leaguemembership lm;
    }*/
    
    /*Join/Invite Methods */
    
    public void inviteByEmail(Invitebyemail invite) {
        em.persist(invite);
    }
    
    public void inviteByAccount(Invitebyid invite) {
        em.persist(invite);
    }
    
    public void removeInvitation(Invitebyid invite) {
        em.remove(invite);
    }
    
    public void removeInvitatation(Invitebyemail invite) {
        em.remove(invite);
    }
    
    /**
     * Removes an invitation corresponding to a parallel join request
     * @param jr 
     */
    public void removeInvitation(Joinrequests jr) {
        Account acc = jr.getAccount();
        League l = jr.getLeague();
        
        CriteriaQuery<Invitebyid> cq = em.getCriteriaBuilder().createQuery(Invitebyid.class);
        Root<Invitebyid> invite = cq.from(Invitebyid.class);
        cq.select(invite);
        cq.where(
                cb.equal(invite.get("invitebyidPK").get("inviteid"), acc.getId()),
                cb.equal(invite.get("invitebyidPK").get("leagueid"), l.getId()));
        TypedQuery<Invitebyid> q = em.createQuery(cq);
        
        List<Invitebyid> corr = q.getResultList();
        for (Invitebyid i : corr) {
            System.out.println("Removing Invitation:\n" + i);
            em.remove(i);
        }
    }
    
    public void addJoinRequest(Joinrequests jr) {
        em.persist(jr);
    }
    
    public void removeJoinRequest(Joinrequests jr) {
        em.remove(jr);
    }
    
    /**
     * Allows for the removal of parallel join requests after acceptance of an invite
     * @param invite 
     */
    public void removeJoinRequest(Invitebyid invite) {
        System.out.println("ENTERING METHOD");
        int rec = invite.getInvitebyidPK().getInviteid();
        int league = invite.getInvitebyidPK().getLeagueid();
        System.out.println("VARIABLES GOTTEN");
        Account acc = invite.getAccount1();
        League l = invite.getLeague();
        System.out.println("ENTITITES GOTTEN");
        
        CriteriaQuery cq = em.getCriteriaBuilder().createQuery(Joinrequests.class);
        Root<Joinrequests> requests = cq.from(Joinrequests.class);
        cq.select(requests);
        cq.where(
                cb.equal(requests.get("joinrequestsPK").get("userid"), rec),
                cb.equal(requests.get("joinrequestsPK").get("leagueid"), league)
                );
        System.out.println("QUERY MADE");
        TypedQuery<Joinrequests> q = em.createQuery(cq);
        System.out.println("TRYING QUERY");
        try {
            Joinrequests jr = q.getSingleResult();
            System.out.println("GOT A RESULT");
            System.out.println("Removing Join Request:\n" + jr);
            System.out.println("REMOVING");
            em.remove(jr);
            System.out.println("REMOVED");
        }
        catch (NoResultException ne) {
            System.out.println("CAUGHT EXCEPTION");
            return;
        }
        catch (ConstraintViolationException ce) {
           for (ConstraintViolation cv : ce.getConstraintViolations()) {
               System.out.println("MESSAGE: " + cv.getMessage() + "Value: " + cv.getInvalidValue() + " of type " +
                       cv.getInvalidValue().getClass().getName());
               
           }
           ce.printStackTrace();
        }
    }
    
    public List<Joinrequests> getJoinRequestsForLeague(League l) {
        CriteriaQuery cq = em.getCriteriaBuilder().createQuery(Joinrequests.class);
        Root<Joinrequests> requests = cq.from(Joinrequests.class);
        cq.select(requests);
        cq.where(
                cb.equal(
                requests.get("joinrequestsPK").get("leagueid"), l.getId()));
        TypedQuery<Joinrequests> q = em.createQuery(cq);
        
        return q.getResultList();
    }
    
    public List<Teamsbyleague> getAllTeamsInLeague(League l) {
        int lid = l.getId();
        Query q = em.createNamedQuery("Teamsbyleague.findByLeagueid");
        q.setParameter("leagueid", lid);
        return q.getResultList();
    }
    
    /*Team Methods*/
    
    public boolean createTeam(Account owner, League league, String name) {
        Integer maxNum;
        maxNum = (Integer) em.createNativeQuery("SELECT MAX(Id) FROM FANTASYTEAMS").getSingleResult();
        Integer newTeamId = maxNum + 1;
        
        Fantasyteams team = new Fantasyteams(newTeamId, name, league, owner);
        
        try {
         em.persist(team);
        }
        catch(EntityExistsException eee) {
            System.out.println("This team already exists");
            return false;
        }
        return true;
    }
    
    public Fantasyteams getTeam(int tid) {
        return em.find(Fantasyteams.class, tid);
    }
    

    
    /**
     * Get's a team with a specified name
     * @param name : The name to look for
     * @param l : The league to look into
     * @return : The team
     */
    public Fantasyteams getTeamByName(String name, League l) {
        CriteriaQuery cq = em.getCriteriaBuilder().createQuery(Fantasyteams.class);
        Root<Fantasyteams> team = cq.from(Fantasyteams.class);
        cq.select(team);
        cq.where(
                cb.equal(team.get("name"), name),
                 cb.equal(team.get("leagueid"), l));
        TypedQuery<Fantasyteams> q = em.createQuery(cq);
        try {
            return q.getSingleResult();
        }
        catch(NoResultException ne) {
            return null;
        } 
    }
    
    /*public List<Fantasyteams> getAllTeamsInLeague(League l) {
        int leagueId = l.getId();
        CriteriaQuery<Fantasyteams> cq = em.getCriteriaBuilder().createQuery(Fantasyteams.class);
        Root<Fantasyteams> team = cq.from(Fantasyteams.class);
        //EntityType<Playerteam> Playerteam_ = Playerteam.getModel();
        Metamodel m = em.getMetamodel();
        EntityType<Fantasyteams> Team_ = m.entity(Fantasyteams.class);
        cq.select(team);
        cq.where(
                cb.equal(team.get(Team_.getSingularAttribute("Leagueid", Integer.class)),
                l.getId()));
        TypedQuery<Fantasyteams> q = em.createQuery(cq);
        
        return q.getResultList();   
    }*/
    
    public int getNumTeamsInLeague(League l) {
        return getAllTeamsInLeague(l).size();
    }
    
    /**
     * Loads a new schedule into the Game History module, to be updated when games are actually played
     * @param l
     * @param schedule 
     */
    public void makeSchedule(League l, HashMap<Integer, ArrayList<TeamPair>> schedule) {
        int leagueId = l.getId();
        Set<Integer> weeks = schedule.keySet();
        for (Integer w : weeks) {
            ArrayList<TeamPair> games = schedule.get(w);
            for (int i = 0; i < games.size(); i++) {
                TeamPair curGame = games.get(i);
                System.out.println("Scheduling " + curGame.first() + " against " + curGame.second() 
                        + " on week " + w);
                Gamehistory gh = new Gamehistory(curGame.first().getId(), curGame.second().getId(), w, l);
                em.persist(gh);
            }
        }
    }
    
    
    
    /*Player Methods*/
    public Player getPlayerById(int id) {
        return em.find(Player.class, id);
    }
    
    
    public List<Player> getPlayerByNameSpec() {
        System.out.println("TRYING QUERY!!!!!!");
        Query q = em.createNamedQuery("Player.findAll");
        //Query q = em.createNativeQuery("SELECT p FROM PLAYER p where p.name = 'AB'", Player.class);
        return q.getResultList();
        
    }
    
    public Stats getPlayerStats(Player p) {
        return em.find(Stats.class, p.getId());
    }
    
    /**
     * Returns all players with the given name
     * @param name : The name to search for
     * @return : A list of all players with that name
     */
    public List<Player> getPlayerByName(String bazooka) {
        CriteriaQuery<Player> cq = em.getCriteriaBuilder().createQuery(Player.class);
        System.out.println("Made query");
        Root<Player> player = cq.from(Player.class);
        Metamodel m = em.getMetamodel();
        System.out.println("GETTING METAMODEL");
        EntityType<Player> Player_ = m.entity(Player.class);
        System.out.println("MAKING ENTITY");
        cq.select(player);
        System.out.println("SELECT");
        //No more mr. Nice Hendrik
        cq.where(
                cb.equal(
                player.get("name"),
                bazooka));
        /*cq.where(
                cb.equal(
                player.get(Player_.getSingularAttribute("Name")),
                bazooka));*/
        System.out.println("Query BUILT");
        TypedQuery<Player> q = em.createQuery(cq);
        System.out.println("Query READY");
        
        return q.getResultList();
    } 
    
    public List<Player> getPlayerByNameAndPosition(String name, Playposition p) {
        //int pId = p.getId();
        
        int pId = p.getId();
        
        CriteriaQuery<Player> cq = em.getCriteriaBuilder().createQuery(Player.class);
        
        Root<Player> player = cq.from(Player.class);
        Metamodel m = em.getMetamodel();
        EntityType<Player> Player_ = m.entity(Player.class);
        cq.select(player);
        cq.where(
                cb.equal(
                player.get(Player_.getSingularAttribute("name")),
                name),
                cb.equal(
                player.get("playposition"),
                pId));
        TypedQuery<Player> q = em.createQuery(cq);
        
        return q.getResultList();
    }
    public boolean assignPlayerToTeam(Player p, Fantasyteams t, League l, short a) {

        
        System.out.println("ASSIGNING PLAYER: " + p);
        
        if(isPlayerAssignedToLeague(p, l)) {
            return false;
        }
        
        //Create the new assignment and persist it to the db
        Playerteam assignment = new Playerteam(p.getId(), t.getId(), l, a);
        em.persist(assignment);
        
        return true;
    }
    
    public List<Playersandpositionsbyteam> getFantasyTeamRoster(Fantasyteams t) {
        CriteriaQuery cq = em.getCriteriaBuilder().createQuery(Playersandpositionsbyteam.class);
        Root<Playersandpositionsbyteam> roster = cq.from(Playersandpositionsbyteam.class);
        cq.select(roster);
        cq.where(
                cb.equal(roster.get("teamid"), t.getId()));
        TypedQuery<Playersandpositionsbyteam> q = em.createQuery(cq);
        
        return q.getResultList();
        
    }
    
    
    public boolean isPlayerInTrade(Player p) {
        CriteriaQuery cq1 = em.getCriteriaBuilder().createQuery(Traderequests.class);
        Root<Traderequests> trade1 = cq1.from(Traderequests.class);
        cq1.select(trade1);
        cq1.where (
                cb.equal(trade1.get("offeredplayerid"), p));
        TypedQuery q1 = em.createQuery(cq1);
        
        List<Traderequests> t1 = q1.getResultList();
        
        CriteriaQuery cq2 = em.getCriteriaBuilder().createQuery(Traderequests.class);
        Root<Traderequests> trade2 = cq2.from(Traderequests.class);
        cq2.select(trade2);
        cq2.where (
                cb.equal(trade2.get("offeredplayerid"), p));
        TypedQuery q2 = em.createQuery(cq2);
        t1.addAll(q2.getResultList());
        if (t1.isEmpty())
            return false;
        else
            return true;
    }

        public boolean isPlayerInTrade(int p) {
        Player player = em.find(Player.class, p);
        CriteriaQuery cq1 = em.getCriteriaBuilder().createQuery(Traderequests.class);
        Root<Traderequests> trade1 = cq1.from(Traderequests.class);
        cq1.select(trade1);
        cq1.where (
                cb.equal(trade1.get("offeredplayerid"), player));
        TypedQuery q1 = em.createQuery(cq1);
        
        List<Traderequests> t1 = q1.getResultList();
        
        CriteriaQuery cq2 = em.getCriteriaBuilder().createQuery(Traderequests.class);
        Root<Traderequests> trade2 = cq2.from(Traderequests.class);
        cq2.select(trade2);
        cq2.where (
                cb.equal(trade2.get("offeredplayerid"), player));
        TypedQuery q2 = em.createQuery(cq2);
        t1.addAll(q2.getResultList());
        if (t1.isEmpty())
            return false;
        else
            return true;
    }

    
    public List<Player> getAllPlayersOfPosition(Playposition p) {
        
        int pId = p.getId();
        CriteriaQuery<Player> cq = em.getCriteriaBuilder().createQuery(Player.class);
        Root<Player> player = cq.from(Player.class);
        Metamodel m = em.getMetamodel();
        EntityType<Player> Player_ = m.entity(Player.class);
        cq.select(player);
        cq.where(
                cb.equal(
                player.get(Player_.getSingularAttribute("playposition", Playposition.class)),
                p));
        TypedQuery<Player> q = em.createQuery(cq);
        
        return q.getResultList();
    }

    /**
     * Get the Playerteam assignment for a particular player and team.
     * @param p : The player to look for
     * @param t : The team they are assigned to
     * @return : The assignment if it exists, null otherwise
     */
    public Playerteam getPlayerAssignment(Player p, Fantasyteams t) {
        return em.find(Playerteam.class, new PlayerteamPK(p.getId(), t.getId()));
    }
    
    public boolean isPlayerAssignedToLeague(Player p, League l) {
  
        
        int leagueId = l.getId();
        //int playerId = p.getId();
        
        
        CriteriaQuery<Playerteam> cq = em.getCriteriaBuilder().createQuery(Playerteam.class);
        Root<Playerteam> Playerteam = cq.from(Playerteam.class);
        //Metamodel m = em.getMetamodel();
        //EntityType<Playerteam> Playerteam_ = m.entity(Playerteam.class);
        cq.select(Playerteam);
        cq.where(
                cb.equal(
                 Playerteam.get("leagueid"),
                l),
                cb.equal(
                Playerteam.get("playerteamPK").get("playerid"),
                p.getId()));
        
        TypedQuery<Playerteam> q = em.createQuery(cq);
        
        try {
            q.getSingleResult();
            System.out.println("WE GOT q");
        }
        catch(NoResultException ne) {
            System.out.println("No one here");
            return false;
        }
        return true;
    }
    
    public List<Playerteam> getAllAssignmentsFromLeague(League l) {
        int leagueId = l.getId();
        CriteriaQuery<Playerteam> cq = em.getCriteriaBuilder().createQuery(Playerteam.class);
        Root<Playerteam> Playerteam = cq.from(Playerteam.class);
        //EntityType<Playerteam> Playerteam_ = Playerteam.getModel();
        Metamodel m = em.getMetamodel();
        EntityType<Playerteam> Playerteam_ = m.entity(Playerteam.class);
        cq.select(Playerteam);
        cq.where(
                cb.equal(Playerteam.get("leagueid"),
                l));
        TypedQuery<Playerteam> q = em.createQuery(cq);
        
        return q.getResultList();   
        
    }
    
    public List<Playerteam> getActivePlayersOfTeam(Fantasyteams t) {
        int teamid = t.getId();
        CriteriaQuery cq = em.getCriteriaBuilder().createQuery(Playerteam.class);
        Root<Playerteam> playerteam = cq.from(Playerteam.class);
        cq.select(playerteam);
        cq.where(
                cb.equal(playerteam.get("teamid"), teamid),
                cb.equal(playerteam.get("active"), (short) 1));
        TypedQuery<Playerteam> q = em.createQuery(cq);
        
        return q.getResultList();
    }
    
    
    public List<Playerteam> getBenchedPlayersOfTeam(Fantasyteams t) {
        int teamid = t.getId();
        CriteriaQuery cq = em.getCriteriaBuilder().createQuery(Playerteam.class);
        Root<Playerteam> playerteam = cq.from(Playerteam.class);
        cq.select(playerteam);
        cq.where(
                cb.equal(playerteam.get("teamid"), teamid),
                cb.equal(playerteam.get("active"), (short) 0));
        TypedQuery<Playerteam> q = em.createQuery(cq);
        
        return q.getResultList();
    }
    
    public Playerteam isPlayerAssignedToTeam(int playerId, int teamId) {
        PlayerteamPK key = new PlayerteamPK(playerId, teamId);
        return em.find(Playerteam.class, key);
    }
    
    public boolean updatePlayerActive(int playerId, int teamId, short active) {
        Playerteam assignment = isPlayerAssignedToTeam(playerId, teamId);
        if (assignment == null)
            return false;
        else {
            assignment.setActive(active);
            em.flush();
            return true;
        }
    }
    
    /**Methods related to trades*/
    
    public List<Traderequests> getTradeRequestsForUser(Fantasyteams team) {
        CriteriaQuery cq = em.getCriteriaBuilder().createQuery(Traderequests.class);
        Root<Traderequests> requests = cq.from(Traderequests.class);
        cq.select(requests);
        cq.where(
                cb.equal(requests.get("receiverteamid"), team));
        TypedQuery<Traderequests> q = em.createQuery(cq);
        
        return q.getResultList();
    }

    public int getHighestTradeId() {
        Integer maxNum;
        try {
            maxNum = (Integer) em.createNativeQuery("SELECT MAX(Id) FROM FANTASYTEAMS").getSingleResult();
            return maxNum;
        }
        catch(NoResultException ne) {
            System.out.println("No trades at all!");
            return 0;
        }
        
    }
    
    /**
     * Proposes a Trade
     * @param team1 : The team offering the trade
     * @param team2 : The team being offered a trade
     * @param p1 : The player being offered
     * @param p2 : The player being requested
     * @return : Whether the attempt succeeded
     */
    public boolean makeTradeRequest(Fantasyteams team1, Fantasyteams team2, Player p1, Player p2) {
        int newNum = getHighestTradeId() + 1;
        Traderequests tr = new Traderequests(newNum, p1, p2, team1, team2);
        em.persist(tr);
        return true;
    }
   
    /**
     * Accepts a trade request, trading the players and updating the status of the request
     * @param tr : The request to accept
     * @return  : Whether the trade succeeded
     */
    public boolean acceptTradeRequest(Traderequests tr) {
        Playerteam p1 = getPlayerAssignment(tr.getDemandedplayerid(), tr.getReceiverteamid());
        Playerteam p2 = getPlayerAssignment(tr.getOfferedplayerid(), tr.getSenderteamid());
        
        if(tradePlayer(p1, p2)) {
            tr.setStatus((short) 1);
            return true;
        }
        return false;
    }
    
    public void declineTradeRequest(Traderequests tr) {
        em.remove(tr);
    }
    
   /**
     * Trades a player between two teams in the same league.  Player must be benched
     * @param p
     * @param t1
     * @param t2
     * @return 
     */
    public boolean tradePlayer(Playerteam p1, Playerteam p2) {
        if (p1.getActiveBool())
            return false;
        else if (p2.getActiveBool())
            return false;
        
        if (p1.getLeagueid() != p2.getLeagueid())
            return false;
        else {
            int team1 = p1.getTeamid();
            p1.setTeamid(p2.getTeamid());
            p2.setTeamid(team1);
            em.flush();
            return true;
        }
    }
    
}
